/*
 * AnimSwitch.java
 * classes : cc.core.animswitch.AnimSwitch
 * @author YuanChao
 * V 1.0.0
 * Create at 2015年8月21日 上午12:01:46
 */
package cc.core.animswitch;

import android.animation.Animator;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

import static cc.core.animswitch.DrawableCompatHelper.getColorStateList;
import static cc.core.animswitch.DrawableCompatHelper.getColors;
import static cc.core.animswitch.DrawableCompatHelper.getPositions;

/**
 * cc.core.animswitch.AnimSwitch
 *
 * @author YuanChao <br/>
 *         create at 2015年8月22日 上午12:01:46
 */
public class AnimSwitch extends View {

    public static final int TYPE_CIRCLE = 1, TYPE_RECTANGLE = 2;

    private int type = TYPE_CIRCLE;

    private Paint bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Paint circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private ValueAnimator transAnim;
    private RectF bgRect, switchRect;
    private OnCheckedChangeListener switchListener;

    private float posX;
    private float switchHeight = 100f;
    private int defWidth = 25;// 单位是dp
    private float switchPadding;
    private int animDuration = 200;
    private float radius;
    private float switchRadius;
    // private float defColor = 0xffa8a6a6;
    // private float colorOffset = Math.abs(defColor - 0xff00a0ea);
    private int[] onRgb = new int[]{0, 160, 234};
    private int[] offRgb = new int[]{168, 166, 166};
    private boolean isChecked;
    private boolean isAnimEnd = true;
    private Drawable checkDrawable, unCheckedDrawable;
    private ArgbEvaluator mArgbEvaluator;
    int colorOn;
    int colorOff;

    public AnimSwitch(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public AnimSwitch(Context context) {
        super(context);
        init(context, null);
    }

    @SuppressLint("NewApi")
    private void init(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray ta = context.obtainStyledAttributes(attrs,
                    R.styleable.AnimSwitch);
            type = ta.getInt(R.styleable.AnimSwitch_switch_type, TYPE_CIRCLE);
            animDuration = ta.getInt(R.styleable.AnimSwitch_anim_duration, 200);
            switchPadding = ta.getDimension(
                    R.styleable.AnimSwitch_switch_padding, 0);
            radius = ta.getFloat(R.styleable.AnimSwitch_radius, 0);
            checkDrawable = ta.getDrawable(R.styleable.AnimSwitch_checked_bg);
            unCheckedDrawable = ta.getDrawable(R.styleable.AnimSwitch_unchecked_bg);
            colorOn = ta.getColor(R.styleable.AnimSwitch_color_open, Color.rgb(onRgb[0], onRgb[1], onRgb[2]));
            colorOff = ta.getColor(R.styleable.AnimSwitch_color_close, Color.rgb(offRgb[0], offRgb[1], offRgb[2]));
            isChecked = ta.getBoolean(R.styleable.AnimSwitch_checked, false);
            ta.recycle();
        }
        bgPaint.setStyle(Paint.Style.FILL);// 充满
        bgPaint.setColor(Color.LTGRAY);
        bgPaint.setColor(isChecked ? colorOff : colorOff);
        circlePaint.setColor(Color.WHITE);
        bgRect = new RectF(0, 0, switchHeight * 2, switchHeight);
        switchRect = new RectF();
        if (bgEnable()) {
            checkDrawable = new CompatDrawable(checkDrawable);
            unCheckedDrawable = new CompatDrawable(unCheckedDrawable);
        } else {
            mArgbEvaluator = new ArgbEvaluator();
        }

        addListener();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            setLayerType(LAYER_TYPE_SOFTWARE, null);
        }
    }

    private void addListener() {
        post(new Runnable() {
            @Override
            public void run() {
                if (isChecked)
                    setOn(false);
                else
                    setOff(false);
                Rect rect = new Rect((int) bgRect.left, (int) bgRect.top, (int) bgRect.right, (int) bgRect.bottom);
                if (bgEnable()) {
                    checkDrawable.setBounds(rect);
                    unCheckedDrawable.setBounds(rect);
                    bgPaint.setColor(Color.BLACK);
                }
            }
        });
        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                toggle();
            }
        });
    }

    private boolean bgEnable() {
        return checkDrawable != null && unCheckedDrawable != null;
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        switchRect.set(0 + switchPadding + posX, 0 + switchPadding,
                switchHeight - switchPadding + posX, switchHeight
                        - switchPadding);
        canvas.drawRoundRect(bgRect, radius, radius, bgPaint);// 第二个参数是x半径，第三个参数是y半径
        //使用自定义的Drawable来进行绘制。否则必须使用canvas.clipPath来实现圆角效果，但它无法消除锯齿。
        if (bgEnable()) {
            checkDrawable.draw(canvas);
            unCheckedDrawable.draw(canvas);
        }
        canvas.drawRoundRect(switchRect, switchRadius, switchRadius,
                circlePaint);// 第二个参数是x半径，第三个参数是y半径
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        // 获取宽度的模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);

        // 获得宽度
        int width = MeasureSpec.getSize(widthMeasureSpec);

        // 计算测量模式
        switch (widthMode) {
            case MeasureSpec.UNSPECIFIED:
            case MeasureSpec.AT_MOST:
                switchHeight = TypedValue.applyDimension(
                        TypedValue.COMPLEX_UNIT_DIP, defWidth, getResources()
                                .getDisplayMetrics());
                break;
            case MeasureSpec.EXACTLY:
                switchHeight = width / 2;
                break;
        }

        // 圆形的边距设置成高度的1/10
        if (switchPadding == 0)
            switchPadding = switchHeight * 0.15f;

        // 如果你设置为矩形，则radius属性无效(为0)
        if (type == TYPE_RECTANGLE) {
            radius = 0;
        } else if (type == TYPE_CIRCLE && radius == 0) {
            // 如果设置为圆形，且半径为0(即没有设置半径)，则显示为圆形。
            radius = switchHeight / 2.0f;// 半径为高度的一半
        }
        bgRect.set(0, 0, switchHeight * 2, switchHeight);

        switchRadius = ((switchHeight - switchPadding * 2) / switchHeight)
                * radius;
        // 设置测量尺寸值
        setMeasuredDimension((int) (switchHeight * 2), (int) switchHeight); // 这里面是原始的大小，需要重新计算可以修改本行
        if (isChecked) {
            posX = switchHeight;
            bgPaint.setColor(Color.rgb(onRgb[0], onRgb[1], onRgb[2]));
        }
    }

    public int getAnimDuration() {
        return animDuration;
    }

    public void setAnimDuration(int animDuration) {
        this.animDuration = animDuration;
    }

    public void startAnim() {

        final float value = isChecked ? switchHeight : -switchHeight;
        if (isChecked) {
            transAnim = ValueAnimator.ofFloat(value);
        } else {
            transAnim = ValueAnimator.ofFloat(switchHeight, 0);
        }
        transAnim.setDuration(animDuration);
        transAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                posX = (Float) animation.getAnimatedValue();
                float progress = posX / switchHeight;
                if (bgEnable()) {
                    unCheckedDrawable.setAlpha((int) ((1 - Math.abs(progress)) * 255));
                    checkDrawable.setAlpha((int) (Math.abs(progress) * 255));
                } else {
                    bgPaint.setColor((Integer) mArgbEvaluator.evaluate(progress, colorOff, colorOn));
                }

                invalidate();
            }
        });
        transAnim.addListener(new Animator.AnimatorListener() {

            @Override
            public void onAnimationStart(Animator animation) {
                isAnimEnd = false;
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                isAnimEnd = true;
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }
        });
        transAnim.start();
    }

    public boolean isOn() {
        return isChecked;
    }

    private void setOn() {
        isChecked = true;
        startAnim();
    }

    private void setOff() {
        isChecked = false;
        startAnim();
    }

    public void setChecked(boolean isChecked) {
        if (isChecked)
            setOn(false);
        else
            setOff(false);
    }

    public boolean isChecked() {
        return isChecked;
    }

    public void toggle(boolean needAnim) {
        if (!isAnimEnd)
            return;
        isChecked = !isChecked;
        if (isChecked)
            setOn(needAnim);
        else
            setOff(needAnim);
        if (switchListener != null)
            switchListener.onCheckedChanged(this, isChecked);
    }

    public void toggle() {
        toggle(true);
    }

    /**
     * <p>
     * 如果你是在进入界面的初始化方法中设置switch的开关状态，请调用此方法，不要调用 {@code #setOn()}
     * 方法，因为此方法会有动画有延时。
     * </p>
     *
     * @param smoothly
     */
    public void setOn(final boolean smoothly) {
        //没没有绘制完成
        if (bgRect.width() == 0) {
            post(new Runnable() {
                @Override
                public void run() {
                    setOn(smoothly);
                }
            });
            return;
        }
        isChecked = true;
        if (!smoothly) {
            posX = switchHeight;
            bgPaint.setColor(Color.rgb(onRgb[0], onRgb[1], onRgb[2]));
            circlePaint.setColor(Color.WHITE);
            if (bgEnable()) {
                unCheckedDrawable.setAlpha(isChecked ? 0 : 255);
                checkDrawable.setAlpha(isChecked ? 255 : 0);
            }
            invalidate();
        } else
            startAnim();
    }

    public void setOff(final boolean smoothly) {
        if (bgRect.width() == 0) {
            post(new Runnable() {
                @Override
                public void run() {
                    setOff(smoothly);
                }
            });
            return;
        }
        isChecked = false;
        if (!smoothly) {
            posX = 0;
            bgPaint.setColor(Color.rgb(offRgb[0], offRgb[1], offRgb[2]));
            circlePaint.setColor(Color.WHITE);
            if (bgEnable()) {
                unCheckedDrawable.setAlpha(isChecked ? 0 : 255);
                checkDrawable.setAlpha(isChecked ? 255 : 0);
            }
            invalidate();
        } else
            startAnim();
    }

    /**
     * 监听回调 cc.core.animswitch.OnSwitchChangedListener
     *
     * @author YuanChao <br/>
     *         create at 2015年8月22日 上午1:18:26
     */
    public interface OnCheckedChangeListener {
        void onCheckedChanged(AnimSwitch animSwitch, boolean isChecked);
    }

    public void setOnCheckedChangeListener(OnCheckedChangeListener switchListener) {
        this.switchListener = switchListener;
    }

    public int[] convertToARGB(int color) {
        int[] colors = new int[3];
        // alpha = Integer.toHexString(Color.alpha(color));
        colors[0] = Color.red(color);
        colors[1] = Color.green(color);
        colors[2] = Color.blue(color);
        return colors;
    }

    private final class CompatDrawable extends Drawable {
        int mAlpha = 255;
        private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        Shader mShader;

        CompatDrawable(Drawable drawable) {
            if (drawable instanceof ColorDrawable) {
                int color = ((ColorDrawable) drawable).getColor();
                mShader = new LinearGradient(0, 0, bgRect.right, 0, color, color
                        , Shader.TileMode.CLAMP);
            } else if (drawable instanceof BitmapDrawable) {
                mShader = new BitmapShader(((BitmapDrawable) drawable).getBitmap(), Shader.TileMode.CLAMP
                        , Shader.TileMode.CLAMP);
            } else if (drawable instanceof GradientDrawable) {
                int[] colors = DrawableCompatHelper.getColors((GradientDrawable) drawable);
                //如果没有渐变的颜色，使用solid颜色填充
                if (colors == null) {
                    ColorStateList colorStateList = DrawableCompatHelper.getColorStateList((GradientDrawable) drawable);
                    if (colorStateList != null) {
                        colors = new int[]{colorStateList.getDefaultColor(), colorStateList.getDefaultColor()};
                    }
                }
                if (colors != null && colors.length > 0) {
                    if (colors.length == 1) {
                        colors = new int[]{colors[0], colors[0]};
                    }
                    mShader = new LinearGradient(0, 0, bgRect.right, 0,
                            colors, DrawableCompatHelper.getPositions((GradientDrawable) drawable,
                            colors.length == 3), Shader.TileMode.CLAMP);
                }
            } else {
                throw new IllegalArgumentException("不支持的drawable类型:必须是Color,图片，或者是shape资源.");
            }
            mAlpha = drawable.getAlpha();
            mPaint.setAlpha(mAlpha);
            setBounds(drawable.getBounds());
            mPaint.setShader(mShader);
        }

        @Override
        public int getAlpha() {
            return mAlpha;
        }

        @Override
        public void draw(Canvas canvas) {
            //绘制背景
            canvas.drawRoundRect(bgRect, radius, radius, mPaint);
        }

        @Override
        public void setAlpha(int alpha) {
            mAlpha = alpha;
            mPaint.setAlpha(alpha);
            invalidateSelf();
        }

        @Override
        public void setColorFilter(@Nullable ColorFilter colorFilter) {

        }

        @Override
        public int getOpacity() {
            return (mAlpha == 255) ?
                    PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
        }
    }


}
